package com.lace.security.jwt.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lace.security.jwt.SecurityProperties;
import com.lace.security.jwt.auth.JwtAuthenticationProcessingFilter;
import com.lace.security.jwt.auth.JwtAuthenticationProvider;
import com.lace.security.jwt.auth.JwtAuthenticationRequestMatcher;
import com.lace.security.jwt.auth.RestAuthenticationEntryPoint;
import com.lace.security.jwt.constant.SecurityConstant;
import com.lace.security.jwt.controller.DefaultUserDetailService;
import com.lace.security.jwt.encoder.Md5PasswordEncoder;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by WangZX on 2018/7/14.
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private RestAuthenticationEntryPoint authenticationEntryPoint;

	@Autowired
	private JwtAuthenticationProvider jwtAuthenticationProvider;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private SecurityProperties settings;
	
	@Autowired 
	private ObjectMapper objectMapper;

	protected JwtAuthenticationProcessingFilter buildAuthenticationProcessingFilter() throws Exception {
		List<String> pathsToSkip = Arrays.asList(settings.getPermitUrl());
		pathsToSkip = new ArrayList<>(pathsToSkip);
		pathsToSkip.add(settings.getLoginProcessUrl());
		List<String> pathsToProcess = Arrays.asList(settings.getProtectUrl());
		JwtAuthenticationRequestMatcher matcher = new JwtAuthenticationRequestMatcher(pathsToSkip, pathsToProcess);
		JwtAuthenticationProcessingFilter filter = new JwtAuthenticationProcessingFilter(settings, objectMapper, matcher);
		filter.setAuthenticationManager(this.authenticationManager);
		return filter;
	}

	@Bean
	public RestTemplate restTemplate() throws Exception {
		return new RestTemplate();
	}

	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) {
		auth.authenticationProvider(jwtAuthenticationProvider);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		if (settings.getRolesUrl() != null){
			for (String roleKey : settings.getRolesUrl().keySet()){
				http.authorizeRequests().antMatchers(settings.getRolesUrl().get(roleKey)).hasRole(roleKey);
			}
		}

		http
        .csrf().disable() // We don't need CSRF for JWT based authentication
        .exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint)
        .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
		.and()
			.authorizeRequests()
				.antMatchers(settings.getLoginProcessUrl()).permitAll()
        .and()
            .authorizeRequests()
                .antMatchers(settings.getPermitUrl()).permitAll() // permit end-point
        .and()
            .authorizeRequests()
                .antMatchers(settings.getProtectUrl()).authenticated() // Protected API End-points
        .and()
            .addFilterBefore(buildAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
	}

	@Bean
	@ConditionalOnMissingBean(UserDetailsService.class)
	public UserDetailsService userDetailsService(){
		DefaultUserDetailService userDetailsService = new DefaultUserDetailService();
		userDetailsService.setPasswordEncoder(passwordEncoder());
		return userDetailsService;
	}

	@Bean
	@ConditionalOnMissingBean(PasswordEncoder.class)
	public PasswordEncoder passwordEncoder() {
		if (StringUtils.equals(settings.getEncoderType(), SecurityConstant.PASSWORD_ENCODER_BCrypt)){
			return new BCryptPasswordEncoder();
		}else if (StringUtils.equals(settings.getEncoderType(),SecurityConstant.PASSWORD_ENCODER_MD5)){
			return new Md5PasswordEncoder();
		}
		throw new RuntimeException("密码加密方式未设置");
	}
}
